1 /** 2 * Provides the basic manipulation functions of images. 3 * 4 * This is a submodule of devisualization.image.manipulation. 5 * 6 * License: 7 * Copyright Devisualization (Richard Andrew Cattermole) 2014 - 2017. 8 * Distributed under the Boost Software License, Version 1.0. 9 * (See accompanying file LICENSE_1_0.txt or copy at 10 * http://www.boost.org/LICENSE_1_0.txt) 11 */ 12 module devisualization.image.manipulation.base; 13 import devisualization.image.interfaces; 14 import devisualization.image.primitives : isImage, ImageColor, isPixelRange; 15 import std.experimental.color : isColor; 16 import stdx.allocator : IAllocator, theAllocator; 17 import std.traits : isPointer; 18 19 /** 20 * Fills an image storage instance with a single color 21 * 22 * Can be used to initialize an image with a set color. 23 * 24 * Params: 25 * image = The image to fill into 26 * value = The color to fill as 27 * 28 * Returns: 29 * The image instance for composability 30 * 31 * Examples: 32 * ------------- 33 * SwappableImage!RGB8 image = ...; 34 * image.fill(RGB8(77, 82, 31)); 35 * ------------- 36 */ 37 ref Image fillOn(Image, Color = ImageColor!Image)(ref Image image, Color value) @nogc @safe if (isImage!Image && is(Image == struct) && !isPointer!Image) { 38 return *fillOn(&image, value); 39 } 40 41 // Ditto 42 Image fillOn(Image, Color = ImageColor!Image)(Image image, Color value) @nogc @safe if (isImage!Image) { 43 foreach(x; 0 .. image.width) { 44 foreach(y; 0 .. image.height) { 45 image.setPixel(x, y, value); 46 } 47 } 48 49 return image; 50 } 51 52 /// 53 unittest { 54 import std.experimental.color; 55 enum RGB8 TheColor = RGB8(78, 82, 11); 56 57 MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2); 58 test.fillOn(TheColor); 59 60 assert(test[0, 0] == TheColor); 61 assert(test[0, 1] == TheColor); 62 assert(test[1, 0] == TheColor); 63 assert(test[1, 1] == TheColor); 64 } 65 66 /** 67 * Fills a range as replacement for an image size. 68 * 69 * Can be used to initialize an image with a set color. 70 * 71 * Params: 72 * image = The image to fill as 73 * value = The color to fill as 74 * 75 * Returns: 76 * An input range that returns value for each pixel in the image 77 * 78 * Examples: 79 * ------------- 80 * SwappableImage!RGB8 image = ...; 81 * foreach(pixel; image.fillRange(RGB8(77, 82, 31))) { 82 * writeln(pixel, " at x:", pixel.x, " y:", pixel.y); 83 * writeln("\tImage has size width:", pixel.imageWidth, " height:", pixel.imageHeight); 84 * } 85 * ------------- 86 */ 87 auto fillRange(Image, Color = ImageColor!Image)(Image image, Color value) @nogc nothrow @safe if (isImage!Image) { 88 return FillRange!Color(image, value); 89 } 90 91 /** 92 * Fills a range as replacement for an image size. 93 * 94 * Can be used to initialize an image with a set color. 95 * 96 * Params: 97 * image = An input range for color to fill as 98 * value = The color to fill as 99 * 100 * Returns: 101 * An input range that returns value for each pixel in the image 102 * 103 * Examples: 104 * ------------- 105 * SwappableImage!RGB8 image = ...; 106 * foreach(pixel; image.fillRange(RGB8(77, 82, 31))) { 107 * writeln(pixel, " at x:", pixel.x, " y:", pixel.y); 108 * writeln("\tImage has size width:", pixel.imageWidth, " height:", pixel.imageHeight); 109 * } 110 * ------------- 111 */ 112 auto fillRange(IRImage, ET = ElementType!IRImage, Color)(IRImage image, Color value) @nogc nothrow @safe if (isPixelRange!IRImage && is(ET == PixelPoint!Color)) { 113 return FillRange!Color(value, 0, 0, image.front.imageWidth, image.front.imageHeight); 114 } 115 116 private { 117 import std.range : ElementType; 118 119 struct FillRange(Color) { 120 private { 121 Color color; 122 size_t x_, y_, image_width, image_height; 123 } 124 125 this(Image)(Image image, Color value) @nogc nothrow @safe { 126 this.color = value; 127 image_width = image.width; 128 image_height = image.height; 129 } 130 131 private this(Color value, size_t x, size_t y, size_t width, size_t height) @nogc nothrow @safe { 132 this.color = value; 133 this.x_ = x; 134 this.y_ = y; 135 this.image_width = width; 136 this.image_height = height; 137 } 138 139 @property { 140 PixelPoint!Color front() @nogc nothrow @safe { 141 return PixelPoint!Color(color, x_, y_, image_width, image_height); 142 } 143 144 bool empty() @nogc nothrow @safe { 145 return x_ >= image_width && y_ >= image_height - 1; 146 } 147 148 FillRange!Color save() @nogc nothrow @safe { 149 return FillRange!Color(color, x_, y_, image_width, image_height); 150 } 151 } 152 153 void popFront() @nogc nothrow @safe { 154 x_++; 155 156 if (empty()) { 157 } else { 158 if (x_ == image_width) { 159 x_ = 0; 160 y_++; 161 } 162 } 163 } 164 } 165 } 166 167 /// 168 unittest { 169 import std.experimental.color; 170 enum RGB8 TheColor = RGB8(78, 82, 11); 171 enum RGB8 TheColor2 = RGB8(6, 93, 11); 172 173 MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2); 174 175 size_t count; 176 foreach(pixel; fillRange(test, TheColor)) { 177 assert(pixel.value == TheColor); 178 count++; 179 } 180 assert(count == 4); 181 182 count = 0; 183 foreach(pixel; fillRange(test, TheColor).fillRange(TheColor2)) { 184 assert(pixel.value == TheColor2); 185 count++; 186 } 187 assert(count == 4); 188 } 189 190 /** 191 * Flips an image horiztonally 192 * 193 * Params: 194 * image = The image to flip 195 * 196 * Returns: 197 * The image for composibility reasons 198 */ 199 ref Image flipHorizontal(Image)(ref Image image) if (isImage!Image && is(Image == struct) && !isPointer!Image) { 200 return *flipHorizontal(&image); 201 } 202 203 /// Ditto 204 Image flipHorizontal(Image)(Image image) if (isImage!Image) { 205 alias Color = ImageColor!Image; 206 207 size_t height = image.height; 208 size_t width = image.width; 209 size_t h2width = width / 2; // is floored as it is an integer not floating point. 210 211 foreach(y; 0 .. height) { 212 size_t x2 = width; 213 foreach(x; 0 .. h2width) { 214 x2--; 215 Color temp; 216 temp = image.getPixel(x, y); 217 218 image.setPixel(x, y, image.getPixel(x2, y)); 219 image.setPixel(x2, y, temp); 220 } 221 } 222 223 return image; 224 } 225 226 /// 227 unittest { 228 import std.experimental.color; 229 MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2); 230 test.flipHorizontal(); 231 } 232 233 /** 234 * Flips an image horizontally 235 * 236 * Will allocate a new range as the input image. 237 * 238 * Params: 239 * image = The image to flip 240 * allocator = Allocator to allocate the wrapper range for 241 * 242 * Returns: 243 * An input range that returns value for each pixel in the image 244 * 245 * See_Also: 246 * rangeOf 247 */ 248 249 auto flipHorizontalRange(Image)(ref Image image, IAllocator allocator=theAllocator()) @safe if (isImage!Image) { 250 return flipHorizontalRange(image.rangeOf(allocator)); 251 } 252 253 /** 254 * Flips an image horizontally 255 * 256 * Params: 257 * image = The image to flip 258 * allocator = The allocator to deallocate the image 259 * ptrToFree = The point to free upon destructor call 260 * 261 * Returns: 262 * An input range that returns value for each pixel in the image 263 */ 264 auto flipHorizontalRange(IRImage, ET = ElementType!IRImage)(IRImage image) @nogc @safe if (isPixelRange!IRImage) { 265 alias Color = typeof(ET.value); 266 267 struct Result { 268 private { 269 IRImage input; 270 size_t h2width; 271 } 272 273 this(IRImage input) @nogc @safe { 274 this.input = input; 275 h2width = input.front.imageWidth / 2; 276 } 277 278 @property { 279 PixelPoint!Color front() @nogc @safe { 280 auto got = input.front; 281 return PixelPoint!Color(got.value, got.imageWidth - got.x, got.y, got.imageWidth, got.imageHeight); 282 } 283 284 bool empty() @safe { 285 return input.empty; 286 } 287 } 288 289 void popFront() @nogc nothrow @safe { 290 input.popFront; 291 } 292 } 293 294 return Result(image); 295 } 296 297 /// 298 unittest { 299 import std.experimental.color; 300 enum RGB8 TheColor = RGB8(78, 82, 11); 301 302 MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2); 303 test.fillOn(TheColor); 304 305 final class Foo { 306 MyTestImage!RGB8 value; 307 alias value this; 308 309 this(MyTestImage!RGB8 value) { 310 this.value = value; 311 } 312 } 313 314 size_t count; 315 foreach(pixel; flipHorizontalRange(test)) { 316 assert(pixel.value == TheColor); 317 count++; 318 } 319 assert(count == 4); 320 321 count = 0; 322 foreach(pixel; flipHorizontalRange(test).flipHorizontalRange()) { 323 assert(pixel.value == TheColor); 324 count++; 325 } 326 assert(count == 4); 327 } 328 329 /** 330 * Flips an image vertical 331 * 332 * Params: 333 * image = The image to flip 334 * 335 * Returns: 336 * The image for composibility reasons 337 */ 338 ref Image flipVertical(Image)(ref Image image) if (isImage!Image && is(Image == struct) && !isPointer!Image) { 339 return *flipVertical(&image); 340 } 341 342 /// Ditto 343 Image flipVertical(Image)(Image image) if (isImage!Image) { 344 alias Color = ImageColor!Image; 345 346 size_t height = image.height; 347 size_t width = image.width; 348 size_t h2height = height / 2; // is floored as it is an integer not floating point. 349 350 foreach(x; 0 .. width) { 351 size_t y2 = height; 352 foreach(y; 0 .. h2height) { 353 y2--; 354 Color temp; 355 temp = image.getPixel(x, y); 356 357 image.setPixel(x, y, image.getPixel(x, y2)); 358 image.setPixel(x, y2, temp); 359 } 360 } 361 362 return image; 363 } 364 365 /// 366 unittest { 367 import std.experimental.color; 368 MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2); 369 test.flipVertical(); 370 } 371 372 /** 373 * Flips an image vertical 374 * 375 * Will allocate a new range as the input image. 376 * 377 * Params: 378 * image = The image to flip 379 * allocator = Allocator to allocate the wrapper range for 380 * 381 * Returns: 382 * An input range that returns value for each pixel in the image 383 * 384 * See_Also: 385 * rangeOf 386 */ 387 auto flipVerticalRange(Image)(ref Image image, IAllocator allocator=theAllocator()) @safe if (isImage!Image) { 388 return flipVerticalRange(image.rangeOf(allocator)); 389 } 390 391 /** 392 * Flips an image horizontally 393 * 394 * Params: 395 * image = The image to flip 396 * 397 * Returns: 398 * An input range that returns value for each pixel in the image 399 */ 400 auto flipVerticalRange(IRImage, ET = ElementType!IRImage)(IRImage image) @nogc @safe if (isPixelRange!IRImage) { 401 alias Color = typeof(ET.value); 402 403 struct Result { 404 private { 405 IRImage input; 406 } 407 408 this(IRImage input) @nogc @safe { 409 this.input = input; 410 } 411 412 @property { 413 PixelPoint!Color front() @nogc @safe { 414 auto got = input.front; 415 return PixelPoint!Color(got.value, got.x, got.imageHeight - got.y, got.imageWidth, got.imageHeight); 416 } 417 418 bool empty() @safe { 419 return input.empty; 420 } 421 422 static if (__traits(hasMember, IRImage, "save")) { 423 auto save() @nogc @safe { 424 return Result(input.save()); 425 } 426 } 427 } 428 429 void popFront() @nogc nothrow @safe { 430 input.popFront; 431 } 432 } 433 434 return Result(image); 435 } 436 437 /// 438 unittest { 439 import std.experimental.color; 440 enum RGB8 TheColor = RGB8(78, 82, 11); 441 442 MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2); 443 test.fillOn(TheColor); 444 445 final class Foo { 446 MyTestImage!RGB8 value; 447 alias value this; 448 449 this(MyTestImage!RGB8 value) { 450 this.value = value; 451 } 452 } 453 454 size_t count; 455 foreach(pixel; flipVerticalRange(test)) { 456 assert(pixel.value == TheColor); 457 count++; 458 } 459 assert(count == 4); 460 461 count = 0; 462 foreach(pixel; flipVerticalRange(test).flipVerticalRange()) { 463 assert(pixel.value == TheColor); 464 count++; 465 } 466 assert(count == 4); 467 }